用C语言实现websocket服务器 |
您所在的位置:网站首页 › websocket c语言编译器 › 用C语言实现websocket服务器 |
Websocket Echo Server Demo 背景 嵌入式设备的应用开发大都依靠C语言来完成,我去研究如何用C语言实现websocket服务器也是为了在嵌入式设备中实现一个ip camera的功能,用户通过网页访问到嵌入式设备的摄像头以及音频,在学习的过程中先实现echo server是最基本的。 主要参考资源 编写 WebSocket 服务器——MDN Linux下用C编写WebSocet服务以响应HTML5的WebSocket请求 具体实现 整个websocket从握手到数据传输帧头的格式不在这里展开,具体参考编写 WebSocket 服务器——MDN,在这里只介绍一下websocket echo server的实现。 头文件及宏定义 #include #include #include #include #include #include #include /*在握手时需要进行sha1编码和base64编码, 在这里用openssl的库来实现*/ #include #include #include #include #define BUFFER_SIZE 1024 #define RESPONSE_HEADER_LEN_MAX 1024 #define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 数据帧头 /*-----------为了便于理解,在这里吧数据帧格式粘出来------------------- 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ --------------------------------------------------------------------*/ typedef struct _frame_head { char fin; char opcode; char mask; unsigned long long payload_length; char masking_key[4]; } frame_head; 封装套接字函数 为了使套接字使用看起来简洁一些,封装一个被动套接字函数,只需要传入监听端口和监听队列个数就可以返回套接字描述符,调用者可以直接用这个描述符accept去接收客户端连接。 int passive_server(int port,int queue) { ///定义sockfd int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(port); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); ///bind,成功返回0,出错返回-1 if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1) { perror("bind"); exit(1); } ///listen,成功返回0,出错返回-1 if(listen(server_sockfd,queue) == -1) { perror("listen"); exit(1); } printf("监听%d端口\n",port); return server_sockfd; } Base64编码函数 握手函数会用到 int base64_encode(char *in_str, int in_len, char *out_str) { BIO *b64, *bio; BUF_MEM *bptr = NULL; size_t size = 0; if (in_str == NULL || out_str == NULL) return -1; b64 = BIO_new(BIO_f_base64()); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); BIO_write(bio, in_str, in_len); BIO_flush(bio); BIO_get_mem_ptr(bio, &bptr); memcpy(out_str, bptr->data, bptr->length); out_str[bptr->length-1] = '\0'; size = bptr->length; BIO_free_all(bio); return size; } 逐行读取函数 握手函数循环调用,每次获得一行字符串,返回下一行开始位置 /** * @brief _readline * read a line string from all buffer * @param allbuf * @param level * @param linebuf * @return */ int _readline(char* allbuf,int level,char* linebuf) { int len = strlen(allbuf); for (;level //next line's point num int level = 0; //all request data char buffer[BUFFER_SIZE]; //a line data char linebuf[256]; //Sec-WebSocket-Accept char sec_accept[32]; //sha1 data unsigned char sha1_data[SHA_DIGEST_LENGTH+1]={0}; //reponse head buffer char head[BUFFER_SIZE] = {0}; if (read(cli_fd,buffer,sizeof(buffer)) strcat(linebuf,GUID); // printf("key:%s\nlen=%d\n",linebuf+19,strlen(linebuf+19)); SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data); // printf("sha1:%s\n",sha1_data); base64_encode(sha1_data,strlen(sha1_data),sec_accept); // printf("base64:%s\n",sec_accept); /* write the response */ sprintf(head, "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade: websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "\r\n",sec_accept); printf("response\n"); printf("%s",head); if (write(cli_fd,head,strlen(head)) temp = *(str+i); *(str+i) = *(str+len-i-1); *(str+len-i-1) = temp; } } 接收及存储数据帧头 调用者传一个数据帧头结构体指针用于获取解析后的帧头 解析过程依照MDN中说的结构解析就好。 int recv_frame_head(int fd,frame_head* head) { char one_char; /*read fin and op code*/ if (read(fd,&one_char,1) perror("read mask"); return -1; } head->mask = (one_char & 0x80) == 0X80; /*get payload length*/ head->payload_length = one_char & 0x7F; if (head->payload_length == 126) { char extern_len[2]; if (read(fd,extern_len,2) char extern_len[8]; if (read(fd,extern_len,8) perror("read masking-key"); return -1; } return 0; } 去掩码函数 从客户端发来的数据是经过异或加密的,我们在解析帧头的时候获取到了掩码,我们通过掩码可以解码出原数据。 /** * @brief umask * xor decode * @param data 传过来时为密文,解码后的明文同样存储在这里 * @param len data的长度 * @param mask 掩码 */ void umask(char *data,int len,char *mask) { int i; for (i=0;i response_head = (char*)malloc(2); response_head[0] = 0x81; response_head[1] = head->payload_length; head_length = 2; } else if (head->payload_length response_head = (char*)malloc(12); response_head[0] = 0x81; response_head[1] = 127; memcpy(response_head+2,head->payload_length,sizeof(unsigned long long)); inverted_string(response_head+2,sizeof(unsigned long long)); head_length = 12; } if(write(fd,response_head,head_length) int ser_fd = passive_server(4444,20); struct sockaddr_in client_addr; socklen_t addr_length = sizeof(client_addr); int conn = accept(ser_fd,(struct sockaddr*)&client_addr, &addr_length); shakehands(conn); while (1) { frame_head head; int rul = recv_frame_head(conn,&head); if (rul < 0) break; // printf("fin=%d\nopcode=0x%X\nmask=%d\npayload_len=%llu\n",head.fin,head.opcode,head.mask,head.payload_length); //echo head send_frame_head(conn,&head); //read payload data char payload_data[1024] = {0}; int size = 0; do { int rul; rul = read(conn,payload_data,1024); if (rul svc_websocket = new WebSocket(wsServer); } catch (evt) { console.log("new WebSocket error:" + evt.data); svc_websocket = null; if (typeof(connCb) != "undefined" && connCb != null) connCb("-1", "connect error!"); return; } //alert(""); svc_websocket.onopen = svc_onOpen; svc_websocket.onclose = svc_onClose; svc_websocket.onmessage = svc_onMessage; svc_websocket.onerror = svc_onError; } function svc_onOpen(evt) { console.log("Connected to WebSocket server."); } function svc_onClose(evt) { console.log("Disconnected"); } function svc_onMessage(evt) { console.log('Retrieved data from server: ' + evt.data); } function svc_onError(evt) { console.log('Error occured: ' + evt.data); } function svc_send(msg) { if (svc_websocket.readyState == WebSocket.OPEN) { svc_websocket.send(msg); } else { console.log("send failed. websocket not open. please check."); } } 开源代码:https://github.com/lhc3538/my-websocket-server |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |